package com.xiaoyun.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xiaoyun.util.sw.*;
import com.xiaoyun.util.sw.json.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: XiaoYun461
 * @Date: 2022/2/19 16:57
 */
@Slf4j
public class Swagger2Ftl {
    /**
     * 包路径
     */
    public static final String THIS_PACK = "com.xiaoyun";
    /**
     * src main java Absolute路径
     */
    public static final String JAVA_FILE_PATH = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "java";
    /**
     * 前缀分包
     */
//    public static final String[] PRE_NAMES = {"Sms", "Admin", "Sys", "Ums", "Cms", "Ims", "Mall", "Pms"};
    public static final String[] PRE_NAMES = null;

    /**
     * 后缀分包
     */
    public static final String[] SUF_NAMES = {"po", "bo", "vo", "param", "result"};
//    public static final String[] SUF_NAMES = null;

    /**
     * 基本属性类型
     */
    public static final String[] BASE_ATTR = {"integer", "string", "boolean", "object", "list"};
    /**
     * 过滤请求
     */
    public static final String[] REQ_METHOD_STR = {"options", "head"};
    /**
     * sw.json 所在路径 默认 resources目录下
     */
    public static final File JSON_FILE_PATH = new ClassPathResource("sw.json").getFile();
    /**
     * entity.ftl 模板所在路径 默认 resources目录下
     */
    public static final File ENTITY_FTL_FILE = new ClassPathResource("entity.ftl").getFile();
    /**
     * controller.ftl 模板所在路径 默认 resources目录下
     */
    public static final File CONTROLLER_FTL_FILE = new ClassPathResource("controller.ftl").getFile();
    /**
     * 方法名分割符 createUsingPOST_10 -> create_10
     */
    public static final String splitKeyWord = "Using";

    /**
     * 是否覆盖原有生成文件
     */
    public static final Boolean IS_COVERED = true;

    /**
     * 测试用例
     *
     * @param args
     */
    public static void main(String[] args) {
        Swagger2Ftl swagger2Ftl = new Swagger2Ftl();
        swagger2Ftl.processDefault();
    }

    public Set<String> processByUrl(String url) {
        String json = HttpUtil.get(url);
        SwRoot swRoot = JSONUtil.toBean(json, SwRoot.class);
        try {
            return processOutputJava(swRoot);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new HashSet<>();
    }

    public Set<String> processDefault() {
        //格式化json对象
        JSONObject json = JSONUtil.readJSONObject(JSON_FILE_PATH, Charset.defaultCharset());
        //json 转为实体
        SwRoot swRoot = JSONUtil.toBean(json, SwRoot.class);
        try {
            return processOutputJava(swRoot);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new HashSet<>();
    }


    public static Set<String> processOutputJava(SwRoot swRoot) throws IOException {
        //获取 root -> definitions
        Map<String, SwPojo> definitions = swRoot.getDefinitions();

        //收集所有实体Entity方法
        List<FtlEntity> ftlEntityList = getEntityList(definitions);
        //收集所有Controller方法
        List<FtlController> ftlControllerList = getControllerList(swRoot);

        //Ftl模板配置设置
        Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        configuration.setDirectoryForTemplateLoading(CONTROLLER_FTL_FILE.getParentFile());
        configuration.setDefaultEncoding("utf-8");

        Template entityTemplate = configuration.getTemplate(ENTITY_FTL_FILE.getName());
        Template controllerTemplate = configuration.getTemplate(CONTROLLER_FTL_FILE.getName());


        Set<String> classNameSet = new HashSet<>();
        if (CollUtil.isNotEmpty(ftlEntityList)) {
            for (FtlEntity ftlEntity : ftlEntityList) {
                process(entityTemplate, ftlEntity, ftlEntity.getOutputFile(), classNameSet);
            }
        }
        if (CollUtil.isNotEmpty(ftlControllerList)) {
            for (FtlController ftlController : ftlControllerList) {
                process(controllerTemplate, ftlController, ftlController.getOutputFile(), classNameSet);
            }
        }
        return classNameSet;
    }

    /**
     * 检查文件是否存在, 检查是否需要覆盖 执行逻辑
     */
    private static void process(Template entityTemplate, Object obj, String outFile, Set<String> classNameSet) {
        File outputFile = new File(outFile);
        if (outputFile == null) {
            return;
        }
        if (outputFile.exists()) {
            if (!IS_COVERED) {
                return;
            }
        }
        File outParentFile = outputFile.getParentFile();
        if (!outParentFile.exists()) {
            outParentFile.mkdirs();
        }
        try (Writer out = new FileWriter(outputFile);) {
            entityTemplate.process(obj, out);
            log.info("生成成功: {}, 位于: {}", outputFile.getName(), outputFile.getAbsolutePath());
            classNameSet.add(outputFile.getName());
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
    }


    private static List<FtlController> getControllerList(SwRoot swRoot) {
        List<FtlController> ftlControllerList = new ArrayList<>();
        List<SwTag> tags = swRoot.getTags();
        //初始化controller 并且填写备注
        initController(ftlControllerList, tags);
        //建立Map对象,方便查找
        Map<String, FtlController> controllerMap = ftlControllerList.stream().collect(Collectors.toMap(FtlController::getClassName, item -> item));
        Map<String, Map<String, SwProperty>> paths = swRoot.getPaths();
        paths.forEach((path, map) -> {
            map.forEach((reqMethod, property) -> {
                //只接受  org.springframework.web.bind.annotation.*Mapping
                if (checkReqMethod(reqMethod)) {
                    return;
                }
                FtlController.Methods methods = new FtlController.Methods();
                methods.setMethodUrl(path);
                String[] tempTags = property.getTags();
                FtlController ftlController = controllerMap.get((checkControllerClassName(tempTags[0])));
                if (tempTags.length == 2) {
                    methods.setTag(tempTags[1]);
                }
                Set<BaseField> baseFieldList = new HashSet<>();
                List<JSONObject> parameters = property.getParameters();
                if (parameters == null) {
                    return;
                }
                for (JSONObject parameter : parameters) {
                    String in = (String) parameter.get("in");
                    //忽略 header
                    if ("header".equals(in)) {
                        continue;
                    }
                    BaseField baseField = new BaseField();
                    String type = getType(ftlController, parameter);
                    String name = (String) parameter.get("name");
                    String description = (String) parameter.get("description");
                    Object aDefault = parameter.get("default");

                    Boolean required = (Boolean) parameter.get("required");
                    if (required) {
                        baseField.setRequired(required);
                    }
                    baseField.setFieldName(name);
                    baseField.setFieldDesc(description);
                    String attr = firstUp(checkAttrName(type));
                    // 判断 url路径是否有{id},从而拼接 @PathVariable
                    attr = checkUrlSetAttr(path, attr, name);
                    if (Objects.nonNull(aDefault)) {
                        baseField.setDefaultValue(String.valueOf(aDefault));
                        //拼接 @RequestParam(defaultValue="")
                        baseField.setFieldAttr("@RequestParam(defaultValue = \"" + baseField.getDefaultValue() + "\")" + attr);
                    } else {
                        baseField.setFieldAttr(attr);
                    }
                    baseFieldList.add(baseField);
                }
                JSONObject jsonObject = property.getResponses().get("200");
                Object schema = jsonObject.get("schema");
                if (schema != null) {
                    Object resBodyDesc = ((JSONObject) schema).get("$ref");
                    if (resBodyDesc == null) {
                        resBodyDesc = ((JSONObject) schema).get("type");
                    }
                    String desc = checkAttrName(String.valueOf(resBodyDesc));
                    methods.setResBodyDesc(desc);
                    //设置import
                    String body = desc2ResBody(desc);
                    if (!checkDataType(body)) {
                        setControllerImport(ftlController, body);
                    }
                    methods.setResBody(checkAttrName(body));
                }
                methods.setReqBody(baseFieldList);
                methods.setReqMethod(firstUp(reqMethod));
                methods.setMethodDesc(property.getSummary());
                methods.setMethodName(checkMethodName(property.getOperationId(), methods.getReqMethod(), methods.getMethodUrl()));

                Set<FtlController.Methods> methodsList = ftlController.getMethodsList();
                if (methodsList == null) {
                    methodsList = new HashSet<>();
                }
                methodsList.add(methods);
                ftlController.setMethodsList(methodsList);
                ftlController.setOutputFile(jointClassPath(ftlController.getPackageName(), ftlController.getClassName()));
            });
        });
        return ftlControllerList;
    }


    /**
     * 方法名分割 createUsingPOST_10 -> create_10
     */
    private static String checkMethodName(String operationId, String reqMethod, String url) {
        if (Objects.isNull(operationId)) {
            return operationId;
        }
        if (url.startsWith("/wxPay")) {
            return operationId;
        }
        String split = splitKeyWord + reqMethod.toUpperCase();
        if (operationId.contains(split)) {
            return operationId.replace(split, "");
        }
        return operationId;
    }

    /**
     * // 判断 url路径是否有{id},从而拼接 @PathVariable
     *
     * @param path url
     * @param attr
     * @return
     */
    private static String checkUrlSetAttr(String path, String attr, String name) {
        if (path.contains("{")) {
            path = path.substring(path.indexOf("{") + 1, path.indexOf("}"));
            if (path.equals(name)) {
                return "@PathVariable " + attr;
            }
        }
        return attr;
    }

    /**
     * 检查请求方法
     *
     * @param reqMethod
     * @return
     */
    private static boolean checkReqMethod(String reqMethod) {
        for (String s : REQ_METHOD_STR) {
            if (s.equals(reqMethod)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取 Attr属性
     *
     * @param ftlController
     * @param parameter
     * @return
     */
    private static String getType(FtlController ftlController, JSONObject parameter) {
        String format = (String) parameter.get("format");
        if (format != null) {
            return checkFormat(format);
        }
        String type = null;
        if ("body".equals(parameter.get("in"))) {
            JSONObject object = (JSONObject) parameter.get("schema");
            Object schemaType = object.get("type");
            String ref = null;
            if (Objects.nonNull(schemaType) && "array".equals(String.valueOf(schemaType))) {
                JSONObject tempJson = ((JSONObject) object.get("items"));
                Object $ref = tempJson.get("$ref");
                if (Objects.nonNull($ref)) {
                    ref = String.valueOf($ref);
                    type = ref2ClassName(ref);
                    setControllerImport(ftlController, type);
                } else {
                    type = type2List(String.valueOf(tempJson.get("type")));
                }
            } else {
                ref = (String) object.get("$ref");
                if (ref == null) {
                    type = String.valueOf(schemaType);
                } else {
                    type = ref2ClassName(ref);
                    if (Validator.hasChinese(type)) {
                        type = chinese2Pinyin(type);
                    }
                    //检查是否是基本数据类型 string object
                    if (!checkDataType(type)) {
                        setControllerImport(ftlController, firstUp(type));
                    }
                }
            }
        } else {
            type = (String) parameter.get("type");
            if ("array".equals(type)) {
                type = (String) ((JSONObject) parameter.get("items")).get("type");
                type = type2List(type);
            } else if ("ref".equals(type)) {
                type = "Integer";
            } else if ("file".equals(type)) {
                type = "MultipartFile";
            }
        }
        return type;
    }

    /**
     * 检查是否是基本数据类型 string object
     *
     * @param type
     * @return
     */
    private static boolean checkDataType(String type) {
        type = type.toLowerCase();
        for (String attrName : BASE_ATTR) {
            if (attrName.equals(type)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置Controller类的 import信息
     *
     * @param ftlController
     * @param type
     */
    private static void setControllerImport(FtlController ftlController, String type) {
        //设置import
        Set<String> importClassName = ftlController.getImportClassName();
        if (importClassName == null) {
            importClassName = new HashSet<>();
        }

        importClassName.add(preNameAndEndNameGroup(type) + "." + type);
        ftlController.setImportClassName(importClassName);
    }

    /**
     * 设置Entity 类的 import信息
     *
     * @param ftlEntity
     * @param type
     */
    private static void setEntityImport(FtlEntity ftlEntity, String type) {
        //设置import
        Set<String> importClassName = ftlEntity.getImportClassName();
        if (importClassName == null) {
            importClassName = new HashSet<>();
        }
        importClassName.add(preNameAndEndNameGroup(type) + "." + type);
        ftlEntity.setImportClassName(importClassName);
    }

    /**
     * 初始化Controoler类 为controller 增加备注 和 根据ClassName设置包名
     *
     * @param ftlControllerList
     * @param tags
     */
    private static void initController(List<FtlController> ftlControllerList, List<SwTag> tags) {
        tags.stream().forEach(key -> {
            String name = key.getName();
            if (name.toLowerCase().endsWith("controller")) {
                FtlController ftlController = new FtlController();
                ftlController.setClassName(checkControllerClassName(name));
                ftlController.setPackageName(preNameAndEndNameGroup(name));
                ftlController.setDesc(key.getDescription());
                ftlControllerList.add(ftlController);
            }
        });
    }

    /**
     * 例如 #/definitions/CommonResult«CommonPage«AdminOmsOrderTableBO»»  -> CommonResult
     *
     * @param desc
     * @return
     */
    private static String desc2ResBody(String desc) {
        if (desc.contains("/")) {
            String[] split = desc.split("/");
            desc = split[split.length - 1];
        }
        if (desc.contains("«")) {
            desc = desc.substring(0, desc.indexOf("«"));
        }
        return desc;

    }

    /**
     * 检查 className 是否含有 中文 或者 "-" 例如 test-controller -> TestController
     *
     * @param name
     * @return
     */
    private static String checkControllerClassName(String name) {
        name = chinese2Pinyin(name);
        if (!name.contains("-")) {
            return name;
        }
        String[] split = name.split("-");
        StringBuilder sb = new StringBuilder();
        for (String s : split) {
            sb.append(firstUp(s));
        }
        return sb.toString();
    }


    /**
     * 拼凑 输出java所在的目录
     *
     * @param packageName 包名 例如: com.xiaoyun
     * @param className   类名
     * @return
     */
    private static String jointClassPath(String packageName, String className) {
        StringBuilder sb = new StringBuilder(JAVA_FILE_PATH);
        String[] split = packageName.split("\\.");
        for (int i = 0; i < split.length; i++) {
            sb.append(File.separator).append(split[i]);
        }
        return sb.append(File.separator).append(className).append(".java").toString();
    }


    /**
     * @param definitions json格式化的实体对象 root-> definitions -> Map<String,SwPojo>
     */
    private static List<FtlEntity> getEntityList(Map<String, SwPojo> definitions) {
        List<FtlEntity> ftlEntityList = new ArrayList<>(definitions.size());
        definitions.forEach((definition, swPojo) -> {
            Map<String, SwProperties> properties = swPojo.getProperties();

            // 有2种情况为空
            // 1. swPojo.type() 为 Object , swPojo.title() 为 Map
            // 2. swPojo.type() 为 Object , swPojo.title() 为 Map«string,object»
            if (properties == null) {
                return;
            }
            String title = swPojo.getTitle();
            if (title == null) {
                return;
            }
            String className = firstUp(title);
            // 含有 « 的 都是嵌套对象,不做处理 例如 CommonResult«CommonPage«UmsAdminNode»»
            // 后续 单个对象中 包含嵌套对象里的 类
            if (className.contains("«")) {
                return;
            }
            FtlEntity ftlEntity = new FtlEntity();
            // 检查是否有中文,将Class类名 中文转为拼音 , & 转为And
            className = chinese2Pinyin(className);
            ftlEntity.setDesc(title);

            List<BaseField> baseFieldList = new ArrayList<>();
            Set<String> requiredStr = swPojo.getRequired();

            properties.forEach((key, value) -> {
                BaseField baseField = new BaseField();
                String type = value.getType();
                // 查看当前 type是否为空; type为空说明是一个引用对象, 一般在 $ref中查找
                checkEntityType(ftlEntity, key, value, baseField, type);
                //设置备注
                baseField.setFieldDesc(value.getDescription());
                //设置 required
                if (CollUtil.isNotEmpty(requiredStr) && requiredStr.contains(baseField.getFieldName())) {
                    baseField.setRequired(true);
                }
                baseFieldList.add(baseField);
            });
            ftlEntity.setFields(baseFieldList);
            ftlEntity.setClassName(className);
            ftlEntity.setPackageName(preNameAndEndNameGroup(className));
            ftlEntity.setOutputFile(jointClassPath(ftlEntity.getPackageName(), ftlEntity.getClassName()));
            ftlEntityList.add(ftlEntity);
        });
        return ftlEntityList;
    }

    private static void checkEntityType(FtlEntity ftlEntity, String key, SwProperties value, BaseField baseField, String type) {
        if (type == null) {
            String $ref = value.get$ref();
            // $ref 一般为 #/definitions/ThreeRecommendRecordDB 这种格式,所以要 去掉 / ,取后面的类
            String name = ref2ClassName($ref);
            String attrName = firstUp(name);
            baseField.setFieldAttr(attrName);
            baseField.setFieldName(firstDown(key));
            setEntityImport(ftlEntity, name);
            // 查看是否为数组,一般都是 List 类型
        } else if ("array".equals(type)) {
            Map<String, String> items = value.getItems();
            if (CollUtil.isNotEmpty(items)) {
                String tempType = items.get("type");
                //判断 tempType, 不为空 说明是基本数据类型; 为空说明是 引用类型 或者是 Map
                if (null != tempType) {
                    baseField.setFieldAttr(type2List(tempType));
                    baseField.setFieldName(firstDown(key));
                } else {
                    String attrName = firstUp(ref2ClassName(items.get("$ref")));
                    // 判断 attrName 是否是Map 开头, 有2种情况
                    // 1. Map
                    // 2. Map«string,object»
                    if (attrName.startsWith("Map")) {
                        if (attrName.contains("«")) {
                            // Map«string,object» -> Map<String,Object>
                            int index = attrName.indexOf("«");
                            String[] split = attrName.substring(index + 1, attrName.length() - 1).split(",");
                            attrName = attrName.substring(0, index) + "<" + firstUp(split[0]) + "," + firstUp(split[1]) + ">";
                        } else {
                            attrName = "Map<String,String>";
                        }
                    } else {
                        setEntityImport(ftlEntity, attrName);
                    }
                    baseField.setFieldName(firstDown(key));
                    baseField.setFieldAttr("List<" + attrName + ">");
                }
            }
            //基本数据类型直接处理
        } else {
            String format = value.getFormat();
            if (format != null) {
                baseField.setFieldAttr(checkFormat(format));
            } else {
                baseField.setFieldAttr(checkAttrName(type));
            }
            baseField.setFieldName(firstDown(key));
        }
    }

    private static String checkFormat(String format) {
        switch (format) {
            case "date-time":
                return "Date";
            case "int32":
                return "Integer";
            case "int64":
                return "Long";
            case "double":
                return "Double";
            default:
                return "String";
        }
    }

    /**
     * 包装成List
     *
     * @param tempType
     * @return
     */
    private static String type2List(String tempType) {
        return "List<" + checkAttrName(tempType) + ">";
    }

    /**
     * 检查类是否含有中文,中文转为拼音, & 转为And
     *
     * @param className
     * @return
     */
    private static String chinese2Pinyin(String className) {
        if (Validator.hasChinese(className)) {
            String[] split = PinyinUtil.getPinyin(className, ",").split(",");
            StringBuilder sb = new StringBuilder();
            for (String str : split) {
                sb.append(firstUp(str));
            }
            if (className.contains("&")) {
                className = sb.toString().replace("&", "And");
            } else {
                className = sb.toString();
            }
        }
        return className;
    }

    /**
     * 基本属性开头大写 integer->Integer, number->BigDecimal
     *
     * @param attr integer ,string
     * @return
     */
    private static String checkAttrName(String attr) {
        for (String attrName : BASE_ATTR) {
            if (attrName.equals(attr)) {
                return firstUp(attrName);
            }
        }
        if ("number".equals(attr)) {
            return "Double";
        }
        if ("array".equals(attr)) {
            return "ArrayList";
        }
        return attr;


    }

    /**
     * 根据类名 分包
     *
     * @param name
     * @return
     */
    private static String preNameAndEndNameGroup(String name) {
        StringBuilder sb = new StringBuilder(THIS_PACK);
        String nameLowerCase = name.toLowerCase();
        if (PRE_NAMES != null) {
            for (int i = 0; i < PRE_NAMES.length; i++) {
                String preName = PRE_NAMES[i];
                if (nameLowerCase.startsWith(PRE_NAMES[i])) {
                    sb.append(".").append(firstDown(preName));
                }
            }
        }

        if (nameLowerCase.endsWith("controller")) {
            sb.append(".").append("controller");
        } else {
            sb.append(".").append("pojo");
        }
        if (SUF_NAMES != null) {
            for (int i = 0; i < SUF_NAMES.length; i++) {
                String preName = SUF_NAMES[i];
                if (nameLowerCase.endsWith(SUF_NAMES[i])) {
                    sb.append(".").append(firstDown(preName));
                }
            }
        }
        return sb.toString();

    }

    /**
     * 首字母大写
     *
     * @param name
     * @return
     */
    private static String firstUp(String name) {
        return StrUtil.upperFirst(name);
    }

    /**
     * 首字母小写
     *
     * @param name
     * @return
     */
    private static String firstDown(String name) {
        return StrUtil.lowerFirst(name);
    }

    /**
     * 例如 : #/definitions/ThreeRecommendRecordDB 转为 ThreeRecommendRecordDB
     *
     * @param $ref
     * @return
     */
    private static String ref2ClassName(String $ref) {
        if (!$ref.contains("/")) {
            return null;
        }
        String[] split = $ref.split("/");
        return split[split.length - 1];
    }


}
